Skip to content

feat: add RAGAS adapter for third-party eval metrics - #618

Merged
aidandaly24 merged 4 commits into
aws:mainfrom
brandonaxu:feat/ragas-adapter-v2
Aug 24, 2026
Merged

feat: add RAGAS adapter for third-party eval metrics#618
aidandaly24 merged 4 commits into
aws:mainfrom
brandonaxu:feat/ragas-adapter-v2

Conversation

@brandonaxu

Copy link
Copy Markdown
Contributor

Summary

Adds RAGASAdapter to the third-party evaluation adapters, alongside the existing DeepEvalAdapter and AutoEvalsAdapter. Wraps any RAGAS metric as an AgentCore code-based Lambda evaluator with the same BaseAdapter pattern.

Design

Scores metrics through RAGAS's per-sample APIs (metric.single_turn_score() / metric.multi_turn_score() for legacy metrics, metric.score(**kwargs) for ragas.metrics.collections metrics) rather than the batch ragas.evaluate() pipeline. This means the adapter itself adds no dependency on datasets/pyarrow/pandas, which makes it compatible with slim ragas builds for size-constrained Lambda deployments.

Three-way dispatch:

  • Legacy single-turn (Faithfulness, ExactMatch, ContextPrecision, ...): SingleTurnSample + single_turn_score()
  • Legacy multi-turn (ToolCallAccuracy, TopicAdherenceScore, AgentGoalAccuracy): MultiTurnSample + multi_turn_score()
  • Collections / decorator metrics (ragas.metrics.collections, @discrete_metric, @numeric_metric): metric.score(**fields) with signature-based field filtering

Features

  • Validates metric.required_columns before scoring — returns MISSING_REQUIRED_FIELD with actionable guidance instead of letting ragas return silent 0.0 scores
  • Parses ground truth and retrieval context embedded in the user message (\n\nReference Answer:\n and \n\nContext:\n separators) since ADOT trace formats have no dedicated fields for these
  • Recovers ranked chunk lists from JSON-serialized embedded contexts, preserving chunk boundaries for rank-aware metrics (LLMContextPrecision)
  • Handles threshold=None metrics (e.g. SemanticSimilarity) and provides an adapter-level threshold= override for collections metrics which carry none
  • Discrete (string-valued) metrics produce categorical labels natively
  • Preserves MetricResult.reason as the explanation
  • ImportError at score time returns MISSING_DEPENDENCY (not a generic crash)
  • Multi-turn messages are stripped of embedded markers so ground truth doesn't leak into the conversation being judged
  • RAGASAdapter is the canonical class; RagasAdapter alias provided

Usage

from bedrock_agentcore.evaluation.custom_code_based_evaluators import custom_code_based_evaluator
from bedrock_agentcore.evaluation.custom_code_based_evaluators.third_party.ragas import RAGASAdapter
from ragas.metrics import Faithfulness
from ragas.llms import LangchainLLMWrapper
from langchain_aws import ChatBedrockConverse

eval_llm = LangchainLLMWrapper(ChatBedrockConverse(
    model_id="us.anthropic.claude-sonnet-4-5-20250929-v1:0",
    region_name="us-east-1",
))
adapter = RAGASAdapter(metric=Faithfulness(), llm=eval_llm)

@custom_code_based_evaluator()
def handler(eval_input, context=None):
    return adapter(eval_input, context)

Known Limitations

  • reference_contexts (ground-truth contexts for reference-comparison metrics) is not defaulted from retrieved contexts — supply it via custom_mapper when a genuine ground-truth source exists
  • reference_tool_calls built from expected_trajectory.toolNames carry names without arguments; arg-accurate comparison needs a custom_mapper with full ToolCall objects
  • reference_topics (TopicAdherenceScore) has no span source — requires custom_mapper
  • Dual-mode metrics (AspectCritic, SimpleCriteriaScore) always score single-turn
  • ragas <1.0 imports datasets when the ragas package is imported; the adapter is compatible with trimmed builds but does not solve that import itself

Dependency Notes

  • Pins ragas>=0.4.3,<1.0.0 — the tested version range
  • Pins langchain-community>=0.3.0,<0.4.2 — langchain-community 0.4.2 removed chat_models.vertexai, which ragas <1.0 imports unconditionally
  • Adds ragas to the evaluation integ test extra-deps in CI
  • uv.lock regenerated for the new extra; incidental bumps to langchain-community resolution only

Testing

  • 48 unit tests covering all dispatch branches, embedded parsing (plain text + JSON chunk lists), validation, multi-turn messages, threshold override, error codes
  • 8 integration tests with real deterministic metrics (legacy ExactMatch, collections ExactMatch, @discrete_metric, ToolCallAccuracy match/mismatch, adapter-imports-without-datasets)
  • All pre-commit hooks pass; uv lock --check passes

@brandonaxu
brandonaxu requested a review from a team August 4, 2026 20:37
@github-actions github-actions Bot added the size/xl PR size: XL label Aug 4, 2026
@brandonaxu brandonaxu changed the title feat: RAGAS adapter for third-party eval metrics feat: add RAGAS adapter for third-party eval metrics Aug 4, 2026
@github-actions github-actions Bot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 4, 2026
jariy17
jariy17 previously approved these changes Aug 10, 2026

@jariy17 jariy17 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

YOu have merge conflicts and small nit

assert result.value is not None


def _make_ragas_evaluator_input(user_prompt, agent_response):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't we use this helper function instead

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — switched to _make_agent_evaluator_input in the latest push. The separate helper was a leftover from earlier development when the shared one used a span format the mappers couldn't parse, but that's been fixed on main since. All ragas integ tests should now use the shared helper.

Remove the ragas-specific _make_ragas_evaluator_input helper — the
shared _make_agent_evaluator_input now uses the correct CloudWatch
span format that the span mappers support.
@brandonaxu
brandonaxu force-pushed the feat/ragas-adapter-v2 branch from 309f760 to 8823edf Compare August 11, 2026 18:11
@github-actions github-actions Bot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 11, 2026
@codecov-commenter

codecov-commenter commented Aug 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.09434% with 21 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main@ddfab76). Learn more about missing BASE report.

Files with missing lines Patch % Lines
...code_based_evaluators/third_party/ragas/adapter.py 90.00% 11 Missing and 10 partials ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main     #618   +/-   ##
=======================================
  Coverage        ?   88.94%           
=======================================
  Files           ?      122           
  Lines           ?    10414           
  Branches        ?     1609           
=======================================
  Hits            ?     9263           
  Misses          ?      760           
  Partials        ?      391           
Flag Coverage Δ
unittests 88.94% <90.09%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

outcome = self._score_legacy(fields)
elif hasattr(self.metric, "multi_turn_score"):
outcome = self._score_multi_turn(fields, span_result)
elif hasattr(self.metric, "ascore") and hasattr(self.metric, "score"):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] I think collections multi-turn metrics bypass the field conversion here. _score_multi_turn() builds the RAGAS messages and reference_tool_calls, but collections metrics go directly to _score_collections() with a string user_input and no reference tools. Real RAGAS 0.4.3 collections.ToolCallAccuracy returned MISSING_REQUIRED_FIELD on the live Strands session despite expectedTrajectory.toolNames; the same metric scored 1.0 when given the converted fields. Could we prepare those fields for collections signatures too and test the real collections class? The current test uses the deprecated legacy class with a custom mapper, so it does not exercise this path.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — _score_collections() now inspects each ascore() parameter and substitutes the converted messages / tool calls when the declared type is a sequence, sharing the conversion with the multi-turn path. Single-turn signatures still get flat strings. Also picks up ToolCallF1, AgentGoalAccuracy*, and TopicAdherence.

Substitution is limited to a {user_input, reference_tool_calls} allowlist rather than "any sequence-typed param" — retrieved_contexts: List[str] is sequence-typed in ~15 collections metrics and would have been clobbered.

One follow-on: with the conversion in place it scored 0.0, not 1.0. toolNames carries no arguments, and ToolCallAccuracy multiplies sequence alignment by argument accuracy, so any agent passing arguments scored 0. The adapter now adopts predicted arguments for argument-less same-named references, in call order. This is an interpretation of toolNames rather than something ragas specifies — it hides no signal since there was never argument ground truth, but happy to make it opt-in.

Tests use the shared _make_agent_evaluator_input (now with tool_calls= / reference_tool_names=) and no custom mapper: real collections and legacy ToolCallAccuracy score 1.0 on the correct tool, 0.0 on the wrong one, plus unit coverage for the substitution and alignment rules.

Note: collections TopicAdherence still needs a custom_mapper for reference_topics — no event field for it, since it's evaluator config rather than per-trace ground truth.

Also included a no-behavior-change simplification pass (unified legacy sample scoring, deduplicated error mapping, stdlib imports at module scope), verified across 17 scenarios against the pre-refactor adapter. Branch is merged up to latest main.

@brandonaxu
brandonaxu force-pushed the feat/ragas-adapter-v2 branch from 14cb808 to 8823edf Compare August 21, 2026 23:22
@github-actions github-actions Bot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 21, 2026
Brandon Xu added 2 commits August 24, 2026 00:44
Collections metrics such as collections.ToolCallAccuracy declare
user_input as a message list and require reference_tool_calls, but they
route to _score_collections(), which passed the flat single-turn fields.
The real metric returned MISSING_REQUIRED_FIELD on a live session even
though expectedTrajectory.toolNames was present.

- Share conversation-field construction between the legacy multi-turn
  and collections paths, selecting it per parameter by declared type so
  single-turn signatures still receive flat strings. Substitution is
  limited to an explicit allowlist because retrieved_contexts is
  sequence-typed in many collections metrics.
- Adopt predicted arguments for argument-less reference tool calls,
  consumed in call order per tool name: toolNames expresses tool
  selection and sequence, so argument-aware metrics no longer count
  every argument as a mismatch.
- Detect bare 'list' annotations, which have no typing origin.
- Cover real collections and legacy ToolCallAccuracy through default
  span mapping, plus the argument-alignment rules.

Also simplifies the adapter with no behavior change: one shared helper
for legacy sample scoring, one place mapping scoring failures to error
outputs, stdlib imports at module scope, and a return-type alias.
Verified against the pre-refactor adapter across 17 scenarios.
@github-actions github-actions Bot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 24, 2026

@aidandaly24 aidandaly24 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for making the fixes. This looks good to me now. I also tested and itw orks as expected.

@aidandaly24
aidandaly24 merged commit d53af20 into aws:main Aug 24, 2026
29 of 31 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/xl PR size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants